package com.fxcharts.widget.linechart;
import javafx.scene.shape.*;
import javafx.scene.paint.Color;
import javafx.scene.*;
import com.fxcharts.widget.linechart.ExLineAxis;
import javafx.scene.paint.RadialGradient;
import javafx.scene.paint.Stop;
import com.fxcharts.widget.linechart.ExLineSeries;
import javafx.scene.text.*;
import javafx.scene.input.MouseEvent;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.animation.Interpolator;
import javafx.scene.layout.Tile;
import javafx.geometry.HPos;
import javafx.scene.effect.*;
import javafx.scene.effect.light.*;
import javafx.animation.transition.*;
import javafx.scene.layout.VBox;
import javafx.scene.layout.HBox;


/**
 * @author Aliaksandr_Spichakou
 * Line chart class
 */
public class ExLineChart extends CustomNode
{
    /**
    
     /**
     * X axis
     */
     public-init var xAxis:ExLineAxis;
     /**
      * Y axis 
     */
     public-init var yAxis:ExLineAxis;
     /**
	*  Line series array
	*/
     public-init var series:ExLineSeries[];
	/**
	* Font for lines labels and tooltips for points
	*/
     public-init var font:Font	= Font{	size: 14}
     /**
     * Circle radius for mark point
     */
     public-init var circleRadius = 5;
     
     /**
     * If true - every point will have circle object
     */
     public-init var showPoints:Boolean=true;
     /**
     * Show points tooltip
     */
     public-init var showPointTooltip:Boolean = true;
     
     /**
     * If false - tooltip will show only is onMouse event
     */
     public-init var alwaysShowTooltip:Boolean = false;
     
      /**
     * Animation of lines
     */
     public-init var animateLines:Boolean=true;
     
     /**
     * Char title
     */
     public-init var chartTitle:String;
     
     // Non-public vars
     
     /**
     * Result group with items
     */     
     var group:Group = Group{};
   /**
     *  Array of class Points
     */
     var points:Points[];
     
     /**
     * Chart sizes
     */
     public-init var hight:Number=200;
     public-init var width:Number=500;     
     
     /**
     * create method
     */
     override function create():Node 
     {   	        
		// set vars fo X axis
		xAxis.zeroX=0;
		xAxis.zeroY=hight;
		xAxis.length=width;
		// set vars for Y axis
		yAxis.zeroX=0;
		yAxis.zeroY=0;
		yAxis.length=hight;
		// paint X and Y axises     
		xAxis.xWidth=hight;    	
		yAxis.xWidth=width;    	
		yAxis.paintY();    	
		xAxis.paintX();
	
		// paint points
		setPoints();
		insert xAxis.group into group.content;
		xAxis.group.toBack();
		insert yAxis.group into group.content;    
		yAxis.group.toBack();
	
		var vbox:VBox=VBox{
							nodeHPos: HPos.CENTER
		};
			
		var title:Text=Text{
		    content: chartTitle;
		    font: font;
		}
		insert title into vbox.content;
		insert group into vbox.content;
		// paint legend in any other location
		var legend=paintLegend(null,null);	
		insert legend into vbox.content;					
				
		return vbox;
    }
    /**
    * Convert X coordinate to local coordinates
    */
    function convertCoordinateX(x:Number):Number
    {
        var w=width/(xAxis.upperBound-xAxis.lowerBound);       
        if(w<0)
        {
            w*=-1;
        }
        var count=xAxis.lowerBound;
        if(count<0)
        {
            count*=-1;
        }
        var newX;
        if(xAxis.lowerBound<=0)
        {
        	newX=(count+x)*w;
        }
        else
        {
            newX=(x-count)*w;
        }
        return newX;
    }
    /**
    * Convert Y coordinate to local coordinates 
    */
    function convertCoordinateY(y:Number):Number
    {       
        var w=hight/(yAxis.upperBound-yAxis.lowerBound);
        if(w<0)
        {
            w*=-1;
        }     
        var count=yAxis.lowerBound;        
        if(count<0)
        {
           count*=-1;
        }   
        var newY;
        if(yAxis.lowerBound<=0)       
        {
         newY=hight-(y+count)*w;
        }        
        else
        {
            newY=hight-(y-count)*w;
        }
        return newY;
    }
    /**
    * Paint points    
    */
    function setPoints():Void
    {
        var vboxLengends:VBox=VBox{};
        for(i in [0..series.size() - 1])
        {
            var serial:ExLineSeries = series[i];
            var data:ExLineData[] = serial.data;
            
            var pointArray:Point[];          
            for(k in [0..data.size()-1])
            {
                var d:ExLineData=data[k];
                var x0 = convertCoordinateX(d.xValue);
                var y0 = convertCoordinateY(d.yValue);              
                                                
                 // Create point object 
                var point;
                if(animateLines==true)
                {
                 point=Point{
							x: this.width/2,
							y: this.hight/2,
							currentX: x0,
							currentY: y0, 
							exLineData:d, startRadius:circleRadius
							};   
                }       
                else
                {
                    point=Point{
    							x: x0,
    							y: y0,
    							currentX: x0,
    							currentY: y0, 
    							exLineData:d, startRadius:circleRadius
    							};
                }
                
                insert point into pointArray;                
                                
                // create Shape object for UI
				// override this method for custom points
				if(showPoints)
            	{
					var node:Node[]=getMarkerPoint(serial,x0,y0,point);            	
					insert node[0] into group.content;
					node[0].toBack();
					insert node[1] into group.content;
					node[1].toFront();            	            	
            	}            	               	
            }  
             // paint line  
            var pointsClass=Points{
            						points: pointArray
            						currentXLeft: pointArray[0].currentX,
            						currentXRight: pointArray[pointArray.size()-1].currentX
            					};                      
            var path = paintLines(serial, pointsClass);              
	    
            insert path into group.content;
            path.toBack();
            insert pointsClass into points;                      
        }            
    }
    /**
    * Paint legend object; 
    * Override this method for custom legend UI
    * @paqram serial - current ExLineSeries object
    * @param  translateX - value for translate to local coordinates (X)
    * @param  translateY - value for translate to local coordinates (Y)
    */
    public function paintLegend(serial:ExLineSeries, pointsClass:Points):Node
    {
        var point:Point = pointsClass.points[0];
        var translateX = point.currentX;
        var translateY = point.currentY;
        var group:Group=Group{
            				translateX: 4*circleRadius
                            translateY: 4*circleRadius                                                                   
        					effect: DropShadow { offsetY: 5 }
        					};                     
        
                                  
	             var circles:Node[];
	             var labels:Node[];
	             for(i in [0..series.size() - 1])
	            {        
	                var sr = series[i];                                  	            
	        		var circle=Circle {  		         		               
	  		         		                radius: bind circleRadius
	  		         		                fill: RadialGradient {
	  		         							centerX: 0.25
	  		         							centerY: 0.25
	  		         							stops: [
	  		         								Stop {
	  		         									color: sr.color
	  		         									offset: 0.0
	  		         								        },
	  		         								Stop {
	  		         									color: Color.BLACK
	  		         								        offset: 1.0
	  		         								        }
	  		         		                    ]
	  		         		                };  		         		                
	        		 }  
	        		 insert circle into circles;		                         
	  		         var label:Text=Text {  		           		         		                
	  		           		         		  font: font                                            
	  		           		         		  content: "{sr.name}"
	  		           		         		  strokeWidth: bind sr.lineWidth
	  		           		         		  onMouseMoved: function( e: MouseEvent ):Void {                                                             
	  		           		         		                	     sr.tUp.playFromStart();   		           		         		                	                                                              							                                                     
												}
	  		           		         	      onMouseExited: function( e: MouseEvent ):Void {                                        
	  		           		         		                	     sr.tUp.pause();  
	  		           		         		     }  
	  		           		         	};
	  		         insert label into labels;
									  		           		         	         			  		     	
	        		}
	        		 var tile:HBox=HBox { 
	        		     	 spacing: labels[0].boundsInLocal.height/2             		                                         
	                         content:
	                         for (k in [0..series.size()-1])
	                             {
	                              [circles[k], labels[k]]
	                             }                                    
	                      };	
	               
	        		insert tile into group.content;   
	        		tile.toFront();           		         		
                                       		
               
        //println(group.content); 
        return group; 
    }
    
    /**
    * Paint line;
    * Override this method for custom line UI
    * @paqram serial - current ExLineSeries object
    * @param  points - array of Point class object
    */
    public function paintLines(serial:ExLineSeries,pointsClass:Points):Node
    {        
        var points=pointsClass.points;
        var path=Path {                
                stroke: bind serial.color
                strokeWidth: bind serial.lineWidth
                effect: DropShadow { offsetY: 4 }                                           
                elements: [
                    MoveTo {
                        x: bind points[0].x
                        y: bind points[0].y
                    },
                    for(i in [0..points.size()-1])
                    {
                      LineTo {
                          		//absolute: true,                          
                                x: bind points[i].x,
                                y: bind points[i].y
                            }  
                    }                    
                ]
		// default animation: pulsation
                 onMouseMoved: function( e: MouseEvent ):Void {                                                             
                     serial.tUp.playFromStart();                                                                            							                                                     
                }
                 onMouseExited: function( e: MouseEvent ):Void {                                        
                    serial.tUp.pause();                    
               }                                
            }
       // animate line     
       if(animateLines)
       {
            if(alwaysShowTooltip==true)
       		{
       			pointsClass.parallelTransition.play();
       		}
       		if(alwaysShowTooltip!=true)
       		{
       		    pointsClass.parallelTransitionWithoutLabels.play();
       		}
       }
       return path;
    }
    /**
    * Paint marker point;
    * Override this method for custom markers
    * @paqram serial - current ExLineSeries object
    * @param  x       - translate X coordinate
    * @param  y       - translate Y coordinate
    * @param  point -  Point class object
    */
    public function getMarkerPoint(serial:ExLineSeries, x:Number,y:Number, point:Point):Node[]
    {
        // TODO: customize by user          
        var popUpLabel:Node;
        if(showPointTooltip)
        {            
        	popUpLabel=getPointLabel(point,x+2*circleRadius,y-2*circleRadius);        	
        	popUpLabel.opacity = 0;	        
        }        
        var circle=Circle {
                translateX: x
				translateY: y 
				opacity: bind point.opacity
                radius: bind point.circleRadius
                effect: DropShadow { offsetY: 4 }
                fill: RadialGradient {
					centerX: 0.25
					centerY: 0.25
					stops: [
						Stop {
							color: serial.color
							offset: 0.0
						        },
						Stop {
							color: Color.BLACK
						        offset: 1.0
						        }
                    ]
                }
       //---END TODO --
                onMouseMoved: function( e: MouseEvent ):Void { 
                    		if(showPointTooltip==true)
                            {                       		                        
	                            popUpLabel.opacity = 1;                                 	                                                        
                            }                            							    
                            point.tUp.playFromStart();                                                  
                        }
               onMouseExited: function( e: MouseEvent ):Void {
                    if(alwaysShowTooltip!=true and alwaysShowTooltip!=true)
                    {
                    	popUpLabel.opacity = 0;                    	
                    }
                    point.tUp.pause();  
               }
                
        }                
        if(showPointTooltip)
        {            	        	        
	        return [circle,popUpLabel];        
        }                                                    
        return [circle,Group{}];
    }
    /**
    * Paint point lable
    * Override this method for custom point labels    
    */
    public function getPointLabel(point:Point, x:Number,y:Number):Node
    {                       
        var pointLable:PointLabel=PointLabel{            								 
        									content:point.exLineData.getText(),
        									x:x,
        									y:y};
        point.labelNode = pointLable; 									
		return pointLable;                
    }
}

class PointLabel extends CustomNode
{
    public-init var content:String;
    public-init var x:Number;
    public-init var y:Number;           
    
    override function create():Node
    {
        var group:Group = Group{};
        if(showPointTooltip)
        {
            var label=Text {
				    		translateX: x
				    		translateY: y    		
				        	content: content        	
				        	font: font 
            	}
            
            var rq = Rectangle {        	               	    
            	    translateX: bind x-label.boundsInLocal.height/10
            	    translateY: bind y-label.boundsInLocal.height+label.boundsInLocal.height/10
            	    width: bind label.boundsInLocal.width + label.boundsInLocal.height/20
            	    height: bind label.boundsInLocal.height + label.boundsInLocal.height/5
            	    effect: DropShadow { offsetY: 5 }
            	   // TODO: customize by user
            	    arcHeight: 5
            	    arcWidth: 5
            	    fill: Color.WHITE
            	    stroke: Color.GRAY
            	    strokeWidth: 1
            	    //---END TODO --
            	}
            insert rq into group.content;
            insert label into group.content;       
        }       
		return group;    
    }    
}

/**
* Class-container for points
*/
public class Points
{
    public-init var points:Point[];
    public-init var width:Number;
    
    public-init var currentXLeft:Number;
    public-init var currentXRight:Number;
    
    public var parallelTransition = SequentialTransition  {	                  
    	          content: [
    	              Timeline {
    	                  	              repeatCount: 1
    	                  	              keyFrames: [    	                  	              
    	                  	                      at(1s) {
													points[0].x => currentXLeft tween Interpolator.LINEAR;
													points[points.size()-1].x => currentXRight tween Interpolator.LINEAR;
					    	                  	 }													  
    	                  	              ]
    	                  	          },
    	                  	          Timeline {
    	                  	               keyFrames: [
    	                  	               						KeyFrame {
    	                  	                                            time: 1s
    	                  	                                            canSkip: true
    	                  	                                            values: [
		    	                  	                                                 for(i in [0..points.size()-1])
																					{    	                  	    								      	                  	    										  		
																						points[i].x => points[i].currentX tween Interpolator.LINEAR																				                	    										  		    	                  	    								  
																					}
							    	                  	                        ]
							    	                  	                },
							    	                  	        KeyFrame {
																		    time:1s
																		    canSkip: true
																		    values: [
																						 for(i in [0..points.size()-1])
																						{    	                  	    								      	                  	    										  																						
																							points[i].y => points[i].currentY tween Interpolator.LINEAR   	                  	    										  		    	                  	    								  
																						}
																		    		]
																		}
																KeyFrame {
																		    time: 1.5s
																		    canSkip: true
																		    values: [
																						 for(i in [0..points.size()-1])
																						{    	                  	    								      	                  	    										  																						
																							points[i].opacity => 1 tween Interpolator.DISCRETE   	                  	    										  		    	                  	    								  
																						}
																		    		]
																		}
																		KeyFrame {
																		    time: 1.5s
																		    canSkip: true
																		    values: [
																						 for(i in [0..points.size()-1])
																						{    	                  	    								      	                  	    										  																						
																							points[i].labelNode.opacity => 1 tween Interpolator.DISCRETE   	                  	    										  		    	                  	    								  
																						}
																		    		]
																		}
    	                  	                              ]
    	                  	          }
    	              
                  ]
              } 
            public var parallelTransitionWithoutLabels = SequentialTransition  {	                  
                	          content: [
                	              Timeline {
                	                  	              repeatCount: 1
                	                  	              keyFrames: [    	                  	              
                	                  	                      at(1s) {
            													points[0].x => currentXLeft tween Interpolator.LINEAR;
            													points[points.size()-1].x => currentXRight tween Interpolator.LINEAR;
            					    	                  	 }													  
                	                  	              ]
                	                  	          },
                	                  	          Timeline {
                	                  	               keyFrames: [
                	                  	               						KeyFrame {
                	                  	                                            time: 1s
                	                  	                                            canSkip: true
                	                  	                                            values: [
            		    	                  	                                                 for(i in [0..points.size()-1])
            																					{    	                  	    								      	                  	    										  		
            																						points[i].x => points[i].currentX tween Interpolator.LINEAR																				                	    										  		    	                  	    								  
            																					}
            							    	                  	                        ]
            							    	                  	                },
            							    	                  	        KeyFrame {
            																		    time: 1s
            																		    canSkip: true
            																		    values: [
            																						 for(i in [0..points.size()-1])
            																						{    	                  	    								      	                  	    										  																						
            																							points[i].y => points[i].currentY tween Interpolator.LINEAR   	                  	    										  		    	                  	    								  
            																						}
            																		    		]
            																		}
            																KeyFrame {
            																		    time: 1.5s
            																		    canSkip: true
            																		    values: [
            																						 for(i in [0..points.size()-1])
            																						{    	                  	    								      	                  	    										  																						
            																							points[i].opacity => 1 tween Interpolator.DISCRETE   	                  	    										  		    	                  	    								  
            																						}
            																		    		]
            																		}            																		
                	                  	                              ]
                	                  	          }
                	              
                              ]
                          }  
}

/**
* Clas-container for point
*/
public class Point
{
    public var labelNode:Node;
    public var opacity:Number=0;
    public-init var exLineData:ExLineData;
    public-init var x:Number;
    public-init var y:Number;
    
    public-init var currentY:Number;
    public-init var currentX:Number;
    
    public-init var startRadius:Number=5;
    public-init var endRadius:Number=6;
    
    public-init var circleRadius:Number=startRadius;
    public var tUp = Timeline {
             repeatCount: Timeline.INDEFINITE
             keyFrames: [
                 KeyFrame {
                     time: 1s
                     canSkip: true
                     values: [
                         circleRadius => endRadius tween Interpolator.LINEAR
                     ]
                 }
                 KeyFrame {
                              time: 2s
                              canSkip: true
                              values: [
                                  circleRadius => startRadius tween Interpolator.LINEAR
                              ]
                          }
             ]
         };
}